import numpy as np
from util.cache import Cache
from threading import RLock
from util.app import Configurable
from util.db import DBManager
from util.file import to_dir
from util.clock import Clock, Time
from util.images import ImagesManager
from util.dot_field_object import to_value, to_obj
from base import Base
import math
import logging
import os
import sys
import cv2
from particle_util import get_distributes
from datetime import datetime as dt


DATE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S'


class Business(Base):
    def __init__(self):
        super(Business, self).__init__()
        self.port = 8241
        self.log_level = "INFO"
        self.log_file = '../logs/log.txt'
        self.stop_password = str(np.random.randint(0, 1000000))
        self.cache_size = 20
        self.img_block_size = 4096
        self.admin_pwd = ''
        self.range = [50, 100, 250, 350, 500, 750]
        self.range_version = 1
        self.pixels_limit = 1000000
        self.images_root = "../images"
        self.images_name_pattern = "{hour:02d}{minute:02d}{second:02d}-{seq:04d}.jpg"
        self.iamges_name_ext = '.jpg'
        self.duration_seconds = 5

        self.cameras = [{
            "id": 1,
            "name": "NoName",
            "input_path": "/Users/apple/MVS_Data/Data",
        }]

        self.result_cache = None
        self.db_manager = None
        self.images_manager = None
        self.clock = Clock()

        self._check_lock = RLock()
        self._checking = False
        self._range_fields = None
        self._range_titles = None

    def log_name(self):
        return None

    def _set_default_logger(self):
        log_dir, file_name, ext = self._get_log_dir_filename_ext()
        if len(log_dir) > 0:
            os.makedirs(log_dir, exist_ok=True)

        self._init_logger(None, self.log_file)
        logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
        # self._init_logger('PlcNet', log_dir + file_name + '_plcnet' + ext)

    def _get_log_dir_filename_ext(self):
        pos = self.log_file.rfind('/')
        if pos < 0:
            pos = self.log_file.rfind('\\')
        pos += 1
        log_dir = self.log_file[:pos]
        file_name = self.log_file[pos:]
        pos = file_name.rfind('.')
        if pos < 0:
            ext = ''
        else:
            ext = file_name[pos:]
            file_name = file_name[:pos]
        return log_dir, file_name, ext

    def _init_logger(self, log_name, log_file):
        logger = logging.getLogger(log_name)
        logger.setLevel(self.log_level)
        logger.addHandler(logging.FileHandler(log_file))

    def get_config(self):
        config = super(Business, self).get_config()
        c = {
            'port': self.port,
            'log_level': self.log_level,
            'log_file': self.log_file,
            'stop_password': self.stop_password,
            'cache_size': self.cache_size,
            'img_block_size': self.img_block_size,
            'admin_pwd': self.admin_pwd,
            'range': self.range,
            'range_version': self.range_version,
            'pixels_limit': self.pixels_limit,
            'images_root': self.images_root,
            'images_name_pattern': self.images_name_pattern,
            'duration_seconds': self.duration_seconds,
            'db': self.db_manager.get_config(),
            'cameras': to_value(self.cameras)
        }
        config.update(c)
        return config

    def set_config(self, config):
        super(Business, self).set_config(config)
        self.set_single_config(config, 'log_level')
        self.set_single_config(config, 'log_file')
        self._set_default_logger()
        self.set_single_config(config, 'port')
        self.set_single_config(config, 'stop_password')
        self.set_single_config(config, 'cache_size')
        self.set_single_config(config, 'img_block_size')
        self.set_single_config(config, 'admin_pwd')
        self.set_single_config(config, 'range')
        self.set_single_config(config, 'range_version')
        self.set_single_config(config, 'pixels_limit')
        self.set_single_config(config, 'images_root')
        self.set_single_config(config, 'images_name_pattern')
        self.set_single_config(config, 'duration_seconds')
        self._set_range_fields()

        ext = self.images_name_pattern
        self.images_name_ext = ext[ext.rfind('.') + 1:]

        self.cameras = config.cameras
        for camera in self.cameras:
            camera.input_path = to_dir(camera.input_path)

        self.result_cache = Cache(self.cache_size)
        self.db_manager = DBManager(config.db)
        self.images_manager = ImagesManager(self.images_root, self.images_name_pattern)
        self.clock.add_task(self.check_input, duration=Time(second=self.duration_seconds))

    def check_input(self):
        with self._check_lock:
            if self._checking:
                return
            self._checking = True
        try:
            for camera in self.cameras:
                try:
                    self._check_directory(camera, camera.input_path)
                except BaseException as e:
                    self.error(e)
        finally:
            with self._check_lock:
                self._checking = False

    def _check_directory(self, camera, input_path):
        names = os.listdir(input_path)
        self.debug("check %s for camera %s(id: %s), found %d images\n" % (input_path, camera.name, camera.id, len(names)))
        for name in names:
            if name.startswith('.'):
                continue
            path = camera.input_path + os.sep + name
            if os.path.isfile(path):
                self.info("read %s\n" % path)
                try:
                    img = cv2.imread(path)
                    result = self.save_img(camera.id, img, dt.fromtimestamp(os.path.getatime(path)))
                    os.remove(path)
                    self.info("camera %s(id: %s): process %s -> img_id: %s, props: %s\n" % (camera.name, camera.id, path, result['img_id'], result['props']))
                except BaseException as e:
                    self.error(e)
            else:
                self._check_directory(camera, path)
                # try:
                #     os.rmdir(path)
                # except:
                #     pass

    def _start(self):
        self.clock.start()

    def _stop(self):
        self.clock.start()

    def _pause(self):
        pass

    def _resume(self):
        pass

    def save_to_cache(self, img):
        return self.result_cache.put(img)

    def get_from_cache(self, id):
        return self.result_cache.get(id)

    def detect_img(self, img):
        return get_distributes(img, self.range)

    def save_img(self, camera_id, img, datetime):
        h, w = img.shape[0], img.shape[1]
        rate = self.pixels_limit / (w * h)
        if rate < 1:
            rate = math.sqrt(rate)
            w = int(w * rate)
            h = int(h * rate)
            img = cv2.resize(img, (w, h))
        dists = get_distributes(img, self.range)
        if len(dists) > 8:
            raise Exception("每张图片最多只能保存8个数据")

        sql = """INSERT INTO distributes(camera_id, img_id, img_time, version{fields}) 
                    VALUES ({camera_id}, '{img_id}', '{datetime}', {version}{props})"""
        fields = ''
        props = ''
        for i, prop in enumerate(dists):
            fields += ', prop%d' % i
            props += ', %f' % prop
        img_id = self.images_manager.save(img, datetime=datetime, info=str(camera_id))
        sql = sql.format(fields=fields, camera_id=camera_id, img_id=img_id,
                         datetime=datetime.strftime(DATE_TIME_FORMAT), version=self.range_version, props=props)
        self.debug('sql: %s\n' % sql)
        with self.db_manager:
            return {
                'id': self.db_manager.insert(sql),
                'camera_id': camera_id,
                'img_id': img_id,
                'props': dists
            }

    def _set_range_fields(self):
        self._range_fields = ''
        self._range_titles = []
        for i in range(len(self.range) - 1):
            self._range_fields += ', prop%d' % i
            self._range_titles.append('%s~%s' % (self.range[i], self.range[i+1]))

    def query(self, camera_id, datetime_start=None, datetime_end=None, limit:int=500):
        """
        查询分布
        :param datetime_start: 开始时间（包含），datetime.datetime类型，None表示没有开始时间
        :param datetime_end: 结束时间（不包含），datetime.datetime类型，None表示没有结束时间
        :return:
        """
        sql = 'SELECT id, camera_id, version, img_id, img_time, create_time %s FROM distributes WHERE camera_id=%s' % (self._range_fields, camera_id)
        if datetime_start is not None:
            sql += " and img_time >= '%s'" % datetime_start.strftime(DATE_TIME_FORMAT)
        if datetime_end is not None:
            sql += " and img_time < '%s'" % datetime_end.strftime(DATE_TIME_FORMAT)
        sql += " ORDER BY img_time limit %d" % limit
        with self.db_manager:
            return {
                'distributes': self.db_manager.select(sql),
                'titles': self._range_titles
            }

    def get_last_distribute(self, camera_id):
        sql = 'SELECT id, version, camera_id, img_id, img_time, create_time %s FROM distributes WHERE camera_id=%s ORDER BY img_time DESC limit 1' % (self._range_fields, camera_id)
        with self.db_manager:
            return {
                'distribute': self.db_manager.select_one(sql),
                'titles': self._range_titles
            }

    def get_path_to_img(self, img_id):
        return self.images_manager.get_full_path(img_id)